/*
 * This file is in charge of responding to the snmp request of the
 * CurrentDialogTable node defined in OPENSER-QUANTUM-SIP-MIB file.
 */
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "CurrentDialogTable.h"

#include "../../mem/shm_mem.h"
#include "snmpstats_globals.h"
#include "dialogCallback.h"
#include "snmpstats_db.h"
#include "QuantumSipServerNotifications.h"

/***************************************
 * Function Declaration
 ***************************************/

void clean_snmp_list(unsigned int ticks, void *param);

/*
 * The pointer to the linked list's pointer.
 * The pointer itself and all the elements on the list are stored in shared memory
 * allocated using shm_malloc() function from kamailio.
 */
struct CurrentDialogTable_entry **listHeadPointer;


/*
 * Initializes the CurrentDialogTable module
 */
void init_CurrentDialogTable(void) {

    listHeadPointer = (struct CurrentDialogTable_entry **)shm_malloc(sizeof(struct CurrentDialogTable_entry *));
    initialize_table_CurrentDialogTable();
    init_dialog_callback();

    //register_timer(clean_snmp_list, listHeadPointer, 60);
}



/*
 * Initialize the CurrentDialogTable table by defining its contents and how it's structured
 */
void
initialize_table_CurrentDialogTable(void) {

    const oid       CurrentDialogTable_oid[] 	= { 1, 3, 6, 1, 4, 1, 3001, 3, 2 };

    const size_t    CurrentDialogTable_oid_len 	= OID_LENGTH(CurrentDialogTable_oid);

    netsnmp_handler_registration *reg;

    netsnmp_iterator_info *iinfo;

    netsnmp_table_registration_info *table_info;

    DEBUGMSGTL(("CurrentDialogTable:init",
                "initializing table CurrentDialogTable\n"));

    reg =
        netsnmp_create_handler_registration("CurrentDialogTable",
                                            CurrentDialogTable_handler,
                                            CurrentDialogTable_oid,
                                            CurrentDialogTable_oid_len,
                                            HANDLER_CAN_RONLY);

    table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
    netsnmp_table_helper_add_indexes(table_info, ASN_UNSIGNED, //ASN_OCTET_STR,
                                     0);

    table_info->min_column = COLUMN_CALLID;
    table_info->max_column = COLUMN_CALLSTATE;

    iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
    iinfo->get_first_data_point = CurrentDialogTable_get_first_data_point;
    iinfo->get_next_data_point = CurrentDialogTable_get_next_data_point;
    iinfo->table_reginfo = table_info;

    netsnmp_register_table_iterator(reg, iinfo);
}

/*****************************************************
 *                List Manipulation                  *
 *****************************************************/
/*
 * Debug function which prints all entries in the list into kamailio's log file.
 */
void CurrentDialogTable_print(struct CurrentDialogTable_entry **theHeadPointer) {

    struct CurrentDialogTable_entry *ptr;
    int theSize = 0;
    ptr = *theHeadPointer;
    LM_INFO("===CurrentDialogTable===\n");
    while (ptr != NULL) {
        theSize++;
        if (ptr->CallId != NULL)
            LM_INFO("No.=%d, CallId=%s", theSize, ptr->CallId);
        ptr = ptr->next;
    }
    LM_INFO("***CurrentDialogTable***\n");
}

/*
 * Return the size of the list.
 */
int CurrentDialogTable_size(struct CurrentDialogTable_entry **theHeadPointer) {

    struct CurrentDialogTable_entry *ptr;
    int theSize = 0;
    ptr = *theHeadPointer;
    while (ptr != NULL) {
        theSize++;
        ptr = ptr->next;
    }
    return theSize;
}

/*
 * Find the entry identified by its call id.
 * Return the pointer of the entry found or NULL if none found.
 */
struct CurrentDialogTable_entry *
CurrentDialogTable_findEntry(struct CurrentDialogTable_entry **theHeadPointer, char *callId) {

    struct CurrentDialogTable_entry *ptr;
    ptr = *theHeadPointer;
    while (ptr != NULL) {
        if (ptr->CallId != NULL && strcmp(callId, ptr->CallId) == 0)
            return ptr;
        ptr = ptr->next;
    }
    return NULL;
}

/*
 * Removes an entry from the list
 */
void CurrentDialogTable_removeEntry(struct CurrentDialogTable_entry **theHeadPointer,
                                    char *callId) {

    struct CurrentDialogTable_entry  *ptr, *prev;

    if (theHeadPointer == NULL || callId == NULL) {
        return;
    }

    for (ptr = *theHeadPointer, prev = NULL;
            ptr != NULL; prev = ptr, ptr = ptr->next) {

	INFO("ptr-callid=%s", ptr->CallId);
	INFO("param-callid=%s", callId);
        if (strcmp(ptr->CallId, callId) == 0)
            break;
    }

    if (prev == NULL && ptr != NULL)   // Found the first element
        *theHeadPointer = ptr->next;
    else if (prev != NULL && ptr != NULL) // Found in another position
        prev->next = ptr->next;

    shm_free(ptr);
}

/*
 * Adds a new entry in the list or upadate its content if the entry
 * exists in the list (identified by call id).
 */
struct CurrentDialogTable_entry *
CurrentDialogTable_addOrUpdateEntry(struct CurrentDialogTable_entry **theHeadPointer,
                                    CurrentDialogTable_context_str *context) {

    int isNewEntry = 0;

    struct CurrentDialogTable_entry *entry;

    // First we find if the entry exists already.
    entry = CurrentDialogTable_findEntry(theHeadPointer, context->CallId);

    if (entry == NULL) { // if none found, we malloc for a new one.
        isNewEntry = 1;
        entry = shm_malloc(sizeof(struct CurrentDialogTable_entry));
        if (!entry) {
            LM_INFO("shm_malloc failed.");
            return NULL;
        }
    }

    // Fill the entry
    strcpy(entry->CallId, context->CallId);
    entry->CallId_len = strlen(context->CallId);

    entry->CallerId      = atoi(context->CallerId);
    entry->CallerProxyId = atoi(context->CallerProxyId);
    entry->CalleeId      = atoi(context->CalleeId);
    entry->CalleeProxyId = atoi(context->CalleeProxyId);
    entry->CallType      = atoi(context->CallType);
    entry->CallState     = atoi(context->CallState);

    entry->HashEntry     = context->HashEntry;
    entry->HashId        = context->HashId;

    // Insert into list if new entry were created.
    if (isNewEntry) {
        entry->next = *theHeadPointer;
        *theHeadPointer = entry;
        LM_INFO("CurretnDialogTable_entry created. CallId=%s", entry->CallId);
    } else
        LM_INFO("CurretnDialogTable_entry updated. CallId=%s", entry->CallId);

    return entry;
}

/*
 * Example iterator hook routines - using 'get_next' to do most of the work
 */
netsnmp_variable_list *
CurrentDialogTable_get_first_data_point(void **my_loop_context,
                                        void **my_data_context,
                                        netsnmp_variable_list *
                                        put_index_data,
                                        netsnmp_iterator_info *mydata) {
    *my_loop_context = *listHeadPointer;
    return CurrentDialogTable_get_next_data_point(my_loop_context,
            my_data_context,
            put_index_data, mydata);
}

netsnmp_variable_list *
CurrentDialogTable_get_next_data_point(void **my_loop_context,
                                       void **my_data_context,
                                       netsnmp_variable_list *
                                       put_index_data,
                                       netsnmp_iterator_info *mydata) {
    struct CurrentDialogTable_entry *entry = (struct CurrentDialogTable_entry *) *my_loop_context;
    netsnmp_variable_list *idx = put_index_data;

    if (entry) {
        snmp_set_var_value(idx, &entry->HashEntry, sizeof(entry->HashEntry));
        idx = idx->next_variable;
        *my_data_context = (void *) entry;
        *my_loop_context = (void *) entry->next;
        return put_index_data;
    } else {
        return NULL;
    }
}


/*
 * Handles requests for the CurrentDialogTable table
 */
int
CurrentDialogTable_handler(netsnmp_mib_handler *handler,
                           netsnmp_handler_registration *reginfo,
                           netsnmp_agent_request_info *reqinfo,
                           netsnmp_request_info *requests) {

    netsnmp_request_info *request;
    netsnmp_table_request_info *table_info;
    struct CurrentDialogTable_entry *table_entry;

    DEBUGMSGTL(("CurrentDialogTable:handler", "Processing request (%d)\n",
                reqinfo->mode));

    switch (reqinfo->mode) {
        /*
         * Read-support (also covers GetNext requests)
         */
    case MODE_GET:
        for (request = requests; request; request = request->next) {
            table_entry = (struct CurrentDialogTable_entry *)
                          netsnmp_extract_iterator_context(request);
            table_info = netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case COLUMN_CALLID:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR,
                                         table_entry->CallId,
                                         table_entry->CallId_len);
                break;
            case COLUMN_CALLERID:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb,
                                           ASN_UNSIGNED,
                                           table_entry->CallerId);
                break;
            case COLUMN_CALLERPROXYID:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb,
                                           ASN_UNSIGNED,
                                           table_entry->CallerProxyId);
                break;
            case COLUMN_CALLEEID:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb,
                                           ASN_UNSIGNED,
                                           table_entry->CalleeId);
                break;
            case COLUMN_CALLEEPROXYID:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb,
                                           ASN_UNSIGNED,
                                           table_entry->CalleeProxyId);
                break;
            case COLUMN_CALLTYPE:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->CallType);
                break;
            case COLUMN_CALLSTATE:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->CallState);
                break;
            default:
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHOBJECT);
                break;
            }
        }
        break;

    }
    return SNMP_ERR_NOERROR;
}

/*
 * Remove the entry if its call id doesn't exist in db.
 * This function is called by a timer.
 */
void clean_snmp_list(unsigned int ticks, void *param) {
    // LM_INFO("Clean up CurrentDialogTable");

    struct CurrentDialogTable_entry **theHeadPointer = param;

    struct CurrentDialogTable_entry  *ptr, *prev;

    int i = 0 ;
    int isActive = 0;

    char **results = NULL;
    int resultCount = CurrentDialogTable_db_get_dialog_list(&results);

    /*
    LM_INFO("results_out=%d", results);
    for(i = 0; i < resultCount; i++)
        LM_INFO("[%d] callId=%s", i, results[i]);
    */

    for (ptr = *theHeadPointer, prev = NULL;
            ptr != NULL; prev = ptr, ptr = ptr->next) { // for each entry

        for(i = 0; i < resultCount; i++) { // verify with db result
            if (strcmp(ptr->CallId, results[i]) == 0) {
                isActive = 1;
                break;
            }
        }

        if (!isActive) { // remove entry if it isn't in db

            if (prev == NULL)   // the first element
                *theHeadPointer = ptr->next;
            else if (ptr != NULL) // Other positions
                prev->next = ptr->next;

            shm_free(ptr);
        }
        isActive = 0;

    } // end for

    // Frees the string array
        for (i = 0; i < resultCount; i++) {
            shm_free(results[i]);
        }
        shm_free(results);
}
